7606
4063
I libri sul linguaggio di programmazione spiegano che i tipi di valore vengono creati sullo stack e i tipi di riferimento vengono creati sull'heap, senza spiegare cosa sono queste due cose. Non ho letto una spiegazione chiara di questo. Capisco cos'è uno stack. Ma,
Dove e cosa sono (fisicamente nella memoria di un computer reale)?
In che misura sono controllati dal sistema operativo o dal linguaggio in fase di esecuzione?
Qual è il loro scopo?
Cosa determina la dimensione di ciascuno di essi?
Cosa ti rende più veloce? 
Lo stack è la memoria messa da parte come spazio di lavoro per un thread di esecuzione. Quando viene chiamata una funzione, un blocco viene riservato in cima allo stack per le variabili locali e alcuni dati di contabilità. Quando quella funzione ritorna, il blocco diventa inutilizzato e può essere utilizzato la prossima volta che viene chiamata una funzione. Lo stack è sempre riservato in un ordine LIFO (last in first out); il blocco riservato più di recente è sempre il blocco successivo da liberare. Questo rende davvero semplice tenere traccia dello stack; liberare un blocco dalla pila non è altro che regolare un puntatore.
L'heap è la memoria riservata all'allocazione dinamica. A differenza dello stack, non esiste un modello imposto per l'allocazione e la deallocazione dei blocchi dall'heap; puoi allocare un blocco in qualsiasi momento e liberarlo in qualsiasi momento. Ciò rende molto più complesso tenere traccia di quali parti dell'heap sono allocate o libere in un dato momento; sono disponibili molti allocatori di heap personalizzati per ottimizzare le prestazioni dell'heap per diversi modelli di utilizzo.
Ogni thread riceve uno stack, mentre in genere esiste un solo heap per l'applicazione (sebbene non sia raro avere più heap per diversi tipi di allocazione).
Per rispondere direttamente alle tue domande:
In che misura sono controllati dal sistema operativo o dal language runtime?
Il sistema operativo alloca lo stack per ogni thread a livello di sistema quando il thread viene creato. In genere il sistema operativo viene chiamato dal Language Runtime per allocare l'heap per l'applicazione.
Qual è il loro scopo?
Lo stack è collegato a un thread, quindi quando il thread esce lo stack viene recuperato. L'heap viene generalmente allocato all'avvio dell'applicazione dal runtime e viene recuperato quando l'applicazione (tecnicamente processo) esce.
Cosa determina la dimensione di ciascuno di essi?
La dimensione dello stack viene impostata quando viene creato un thread. La dimensione dell'heap viene impostata all'avvio dell'applicazione, ma può aumentare in base alla necessità di spazio (l'allocatore richiede più memoria dal sistema operativo).
Cosa ti rende più veloce?
Lo stack è più veloce perché il modello di accesso rende banale allocare e rilasciare la memoria da esso (un puntatore / intero viene semplicemente incrementato o decrementato), mentre l'heap ha una contabilità molto più complessa coinvolta in un'allocazione o deallocazione. Inoltre, ogni byte nello stack tende a essere riutilizzato molto frequentemente, il che significa che tende ad essere mappato nella cache del processore, rendendolo molto veloce. Un altro problema di prestazioni per l'heap è che l'heap, essendo principalmente una risorsa globale, in genere deve essere sicuro per il multi-threading, ovvero ogni allocazione e deallocazione deve essere - tipicamente - sincronizzata con "tutti" gli altri accessi all'heap nel programma.
Una chiara dimostrazione:
Fonte immagine: vikashazrati.wordpress.com
|
Pila:
Memorizzato nella RAM del computer proprio come l'heap.
Le variabili create nello stack usciranno dall'ambito e verranno deallocate automaticamente.
Molto più veloce da allocare rispetto alle variabili nell'heap.
Implementato con una struttura dati dello stack effettiva.
Memorizza i dati locali, gli indirizzi di ritorno, utilizzati per il passaggio dei parametri.
Può avere uno stack overflow quando viene utilizzata una quantità eccessiva di stack (principalmente da ricorsione infinita o troppo profonda, allocazioni molto grandi).
I dati creati nello stack possono essere utilizzati senza puntatori.
Useresti lo stack se sai esattamente quanti dati devi allocare prima della compilazione e non è troppo grande.
Di solito ha una dimensione massima già determinata all'avvio del programma.
Mucchio:
Memorizzato nella RAM del computer proprio come lo stack.
In C ++, le variabili sull'heap devono essere eliminate manualmente e non devono mai uscire dall'ambito. I dati vengono liberati con delete, delete [] o free.
Più lento da allocare rispetto alle variabili nello stack.
Utilizzato su richiesta per allocare un blocco di dati per l'utilizzo da parte del programma.
Può avere frammentazione quando sono presenti molte allocazioni e deallocazioni.
In C ++ o C, i dati creati sull'heap saranno puntati da puntatori e allocati rispettivamente con new o malloc.
Può avere errori di allocazione se viene richiesta l'allocazione di un buffer troppo grande.
Utilizzeresti l'heap se non sai esattamente di quanti dati avrai bisogno in fase di esecuzione o se devi allocare molti dati.
Responsabile per perdite di memoria.
Esempio:
int foo ()
{
char * pBuffer; // <- niente ancora allocato (escluso il puntatore stesso, che è allocato qui sullo stack).
bool b = true; // Allocato nello stack.
se (b)
{
// Crea 500 byte nello stack
buffer di caratteri [500];
// Crea 500 byte nell'heap
pBuffer = new char [500];
} // <- il buffer è deallocato qui, pBuffer no
} // <--- oops c'è una perdita di memoria, avrei dovuto chiamare delete [] pBuffer;
|
Il punto più importante è che heap e stack sono termini generici per i modi in cui la memoria può essere allocata. Possono essere implementati in molti modi diversi e i termini si applicano ai concetti di base.
In una pila di oggetti, gli elementi si trovano uno sopra l'altro nell'ordine in cui sono stati posizionati e puoi rimuovere solo quello in alto(senza rovesciare l'intera cosa).
La semplicità di uno stack è che non è necessario mantenere una tabella contenente un record di ciascuna sezione della memoria allocata; l'unica informazione di stato di cui hai bisogno è un singolo puntatore alla fine dello stack. Per allocare e de-allocare, devi solo aumentare e diminuire quel singolo puntatore. Nota: a volte è possibile implementare uno stack per iniziare all'inizio di una sezione di memoria e estendersi verso il basso anziché crescere verso l'alto.
In un heap, non esiste un ordine particolare nel modo in cui vengono posizionati gli elementi. Puoi raggiungere e rimuovere gli elementi in qualsiasi ordine perché non è presente un elemento "superiore" chiaro.
L'allocazione dell'heap richiede il mantenimento di un record completo di quale memoria è allocata e cosa non lo è, oltre a una manutenzione generale per ridurre la frammentazione, trovare segmenti di memoria contigui abbastanza grandi da adattarsi alla dimensione richiesta e così via. La memoria può essere rilasciata in qualsiasi momento lasciando spazio libero. A volte un allocatore di memoria eseguirà attività di manutenzione come la deframmentazione della memoria spostando la memoria allocata o la raccolta di dati inutili, identificando in fase di esecuzione quando la memoria non è più nell'ambito e deallocandola.
Queste immagini dovrebbero fare un lavoro abbastanza buono nel descrivere i due modi di allocare e liberare memoria in uno stack e in un mucchio. Yum!
In che misura sono controllati dal sistema operativo o dal language runtime?
Come accennato, heap e stack sono termini generali e possono essere implementati in molti modi. I programmi per computer in genere hanno uno stack chiamato stack di chiamate che memorizza le informazioni relative alla funzione corrente, come un puntatore a qualsiasi funzione da cui è stato chiamato e qualsiasi variabile locale. Poiché le funzioni chiamano altre funzioni e quindi restituiscono, lo stack aumenta e si riduce per contenere le informazioni dalle funzioni più in basso nello stack di chiamate. Un programma non ha realmente il controllo del runtime su di esso; è determinato dal linguaggio di programmazione, dal sistema operativo e persino dall'architettura del sistema.
Un heap è un termine generico utilizzato per qualsiasi memoria allocata dinamicamente e in modo casuale; cioè fuori servizio. La memoria viene in genere allocata dal sistema operativo, con l'applicazione che chiama le funzioni API per eseguire questa allocazione. C'è un bel po 'di overhead richiesto nella gestione della memoria allocata dinamicamente, che di solito è gestita dal codice di runtime del linguaggio di programmazione o dell'ambiente utilizzato.
Qual è il loro scopo?
Lo stack di chiamate è un concetto di livello così basso da non essere correlato all '"ambito" nel senso di programmazione. Se disassembli del codice vedrai riferimenti relativi allo stile del puntatore a parti dello stack, ma per quanto riguarda un linguaggio di livello superiore, il linguaggio impone le proprie regole di ambito. Un aspetto importante di uno stack, tuttavia, è che una volta che una funzione ritorna, qualsiasi cosa locale a quella funzione viene immediatamente liberata dallo stack. Funziona nel modo in cui ti aspetteresti che funzioni dato come funzionano i tuoi linguaggi di programmazione. In un mucchio, è anche difficile da definire. Lo scope è tutto ciò che è esposto dal sistema operativo, ma il tuo linguaggio di programmazione probabilmente aggiunge le sue regole su cosa sia uno "scope" nella tua applicazione. L'architettura del processore e il sistema operativo utilizzano l'indirizzamento virtuale, che il processore traduce in indirizzi fisici e ci sono errori di pagina, ecc. Tengono traccia di quali pagine appartengono a quali applicazioni. Non devi mai preoccuparti di questo, però, perché usi semplicemente il metodo utilizzato dal tuo linguaggio di programmazione per allocare e liberare memoria e controllare gli errori (se l'allocazione / liberazione fallisce per qualsiasi motivo).
Cosa determina la dimensione di ciascuno di essi?
Di nuovo, dipende dalla lingua, dal compilatore, dal sistema operativo e dall'architettura. Uno stack è solitamente pre-allocato, perché per definizione deve essere memoria contigua. Il compilatore del linguaggio o il sistema operativo determinano la sua dimensione. Non si memorizzano enormi blocchi di dati nello stack, quindi sarà abbastanza grande da non essere mai completamente utilizzato, tranne nei casi di ricorsione infinita indesiderata (da qui, "overflow dello stack") o altre decisioni di programmazione insolite.
Un heap è un termine generico per tutto ciò che può essere allocato dinamicamente. A seconda del modo in cui la guardi, cambia continuamente dimensione. Nei moderni processori e sistemi operativi il modo esatto in cui funziona è comunque molto astratto, quindi normalmente non devi preoccuparti molto di come funziona in profondità, tranne che (nelle lingue in cui ti consente) non devi usare la memoria che non hai ancora allocato o memoria che hai liberato.
Cosa ti rende più veloce?
Lo stack è più veloce perché tutta la memoria libera è sempre contigua. Non è necessario mantenere alcun elenco di tutti i segmenti di memoria libera, solo un singolo puntatore all'inizio dello stack corrente. I compilatori di solito memorizzano questo puntatore in un registro speciale e veloce per questo scopo. Inoltre, le operazioni successive su uno stack sono solitamente concentrate all'interno di aree di memoria molto vicine, il che a un livello molto basso è utile per l'ottimizzazione da parte del processore su diecache.
|
(Ho spostato questa risposta da un'altra domanda che era più o meno una vittima di questa.)
La risposta alla tua domanda è specifica dell'implementazione e può variare a seconda dei compilatori e delle architetture del processore. Tuttavia, ecco una spiegazione semplificata.
Sia lo stack che l'heap sono aree di memoria allocate dal sistema operativo sottostante (spesso memoria virtuale mappata su richiesta alla memoria fisica).
In un ambiente multi-thread, ogni thread avrà il proprio stack completamente indipendente ma condividerà l'heap. L'accesso simultaneo deve essere controllato sull'heap e non è possibile sullo stack.
Il mucchio
L'heap contiene un elenco collegato di blocchi utilizzati e liberi. Le nuove allocazioni sull'heap (da new o malloc) vengono soddisfatte creando un blocco adatto da uno dei blocchi liberi. Ciò richiede l'aggiornamento dell'elenco dei blocchi sull'heap. Queste meta informazioni sui blocchi nell'heap vengono anche memorizzate nell'heap spesso in una piccola area proprio di fronte a ogni blocco.
Man mano che l'heap cresce, i nuovi blocchi vengono spesso allocati da indirizzi inferiori a indirizzi superiori. Quindi puoi pensare all'heap come a un mucchio di blocchi di memoria che cresce di dimensioni man mano che la memoria viene allocata. Se l'heap è troppo piccolo per un'allocazione, la dimensione può spesso essere aumentata acquisendo più memoria dal sistema operativo sottostante.
Allocare e deallocare molti piccoli blocchi può lasciare l'heap in uno stato in cui ci sono molti piccoli blocchi liberi intervallati tra i blocchi usati. Una richiesta per allocare un blocco di grandi dimensioni potrebbe non riuscire perché nessuno dei blocchi liberi è sufficientemente grande da soddisfare la richiesta di allocazione anche se la dimensione combinata dei blocchi liberi può essere sufficientemente grande. Questa è chiamata frammentazione dell'heap.
Quando un blocco usato adiacente a un blocco libero viene deallocato, il nuovo blocco libero può essere unito al blocco libero adiacente per creare un blocco libero più grande riducendo efficacemente la frammentazione dell'heap.
La pila
Lo stack funziona spesso in stretta tandem con un registro speciale sulla CPU chiamato stack pointer. Inizialmente il puntatore dello stack punta alla parte superiore dello stack (l'indirizzo più alto sullo stack).
La CPU ha istruzioni speciali per inserire i valori nello stack e rimuoverli dallo stack. Ogni pressione memorizza il valore nella posizione corrente del puntatore dello stack e diminuisce il puntatore dello stack. Un pop recupera il valore puntato dal puntatore dello stack e quindi aumenta il puntatore dello stack (non essere confuso dal fatto che l'aggiunta di un valore allo stack diminuisce il puntatore dello stack e la rimozione di un valore lo aumenta. Ricorda che lo stack cresce il fondo). I valori memorizzati e recuperati sono i valori dei registri della CPU.
Quando viene chiamata una funzione, la CPU utilizza istruzioni speciali che spingono il puntatore dell'istruzione corrente, ovvero l'indirizzo del codice in esecuzione sullo stack. La CPU quindi passa alla funzione impostando l'estensione
puntatore dell'istruzione all'indirizzo della funzione chiamata. Successivamente, quando la funzione ritorna, il vecchio puntatore all'istruzione viene estratto dallo stack e l'esecuzione riprende dal codice subito dopo la chiamata alla funzione.
Quando si immette una funzione, il puntatore allo stack viene ridotto per allocare più spazio sullo stack per le variabili locali (automatiche). Se la funzione ha una variabile locale a 32 bit, quattro byte vengono messi da parte nello stack. Quando la funzione ritorna, il puntatore dello stack viene spostato indietro per liberare l'area allocata.
Se una funzione ha parametri, questi vengono inseriti nello stack prima della chiamata alla funzione. Il codice nella funzione è quindi in grado di spostarsi verso l'alto nello stack dal puntatore dello stack corrente per individuare questi valori.
Le chiamate di funzione di annidamento funzionano come un fascino. Ogni nuova chiamata allocherà i parametri della funzione, l'indirizzo di ritorno e lo spazio per le variabili locali e questi record di attivazione possono essere impilati per le chiamate annidate e si svolgeranno nel modo corretto quando le funzioni ritornano.
Poiché lo stack è un blocco limitato di memoria, è possibile causare un overflow dello stack chiamando troppe funzioni annidate e / o allocando troppo spazio per le variabili locali. Spesso l'area di memoria utilizzata per lo stack è impostata in modo tale che la scrittura sotto il fondo (l'indirizzo più basso) dello stack attiverà un trap o un'eccezione nella CPU. Questa condizione eccezionale può quindi essere rilevata dal runtime e convertita in una sorta di eccezione di overflow dello stack.
È possibile allocare una funzione nell'heap anziché in uno stack?
No, i record di attivazione per le funzioni (cioè le variabili locali o automatiche) sono allocati nello stack che viene utilizzato non solo per memorizzare queste variabili, ma anche per tenere traccia delle chiamate di funzioni annidate.
Il modo in cui viene gestito l'heap dipende dall'ambiente di runtime. C usa malloc e C ++ usa new, ma molti altri linguaggi hanno la garbage collection.
Tuttavia, lo stack è una funzionalità di livello più basso strettamente legata all'architettura del processore. Crescere il mucchio quando non c'è abbastanza spazio non è troppo difficile da allorapuò essere implementato nella chiamata della libreria che gestisce l'heap. Tuttavia, aumentare lo stack è spesso impossibile poiché l'overflow dello stack viene scoperto solo quando è troppo tardi; e chiudere il thread di esecuzione è l'unica opzione praticabile.
|
Nel codice C # seguente
public void Method1 ()
{
int i = 4;
int y = 2;
class1 cls1 = nuova classe1 ();
}
Ecco come viene gestita la memoria
Variabili locali che devono durare solo finché l'invocazione della funzione va nello stack. L'heap viene utilizzato per variabili la cui durata non conosciamo in anticipo, ma ci aspettiamo che durino un po '. Nella maggior parte dei linguaggi è fondamentale sapere in fase di compilazione quanto è grande una variabile se vogliamo memorizzarla nello stack.
Gli oggetti (che variano di dimensioni man mano che li aggiorniamo) vanno nell'heap perché non sappiamo al momento della creazione quanto dureranno. In molte lingue l'heap viene raccolto in modo obsoleto per trovare oggetti (come l'oggetto cls1) che non hanno più riferimenti.
In Java, la maggior parte degli oggetti va direttamente nell'heap. In linguaggi come C / C ++, gli struct e le classi possono spesso rimanere in pila quando non hai a che fare con i puntatori.
Ulteriori informazioni possono essere trovate qui:
La differenza tra lo stack e l'allocazione della memoria heap «timmurphy.org
e qui:
Creazione di oggetti sullo stack e sull'heap
Questo articolo è la fonte dell'immagine sopra: Sei importanti concetti .NET: Stack, heap, tipi di valore, tipi di riferimento, boxing e unboxing - CodeProject
ma tieni presente che potrebbe contenere alcune imprecisioni.
|
Lo Stack
Quando chiamate una funzione, gli argomenti di quella funzione più qualche altro sovraccarico vengono messi in pila. Anche alcune informazioni (come dove andare al ritorno) sono memorizzate lì.
Quando dichiari una variabile all'interno della tua funzione, anche quella variabile viene allocata nello stack.
La deallocazione dello stack è piuttosto semplice perché si dealloca sempre nell'ordine inverso in cui si alloca. Le cose dello stack vengono aggiunte quando si accede alle funzioni, i dati corrispondenti vengono rimossi quando si esce. Ciò significa che tendi a rimanere all'interno di una piccola regione dello stack a meno che non chiami molte funzioni che chiamano molte altre funzioni (o crei una soluzione ricorsiva).
The Heap
L'heap è un nome generico per la posizione in cui vengono inseriti i dati creati al volo. Se non sai quante astronavi creerà il tuo programma, probabilmente utilizzerai il nuovo operatore (o malloc o equivalente) per creare ciascuna navicella spaziale. Questa allocazione durerà per un po ', quindi è probabile che libereremo le cose in un ordine diverso da quello in cui le abbiamo create.
Pertanto, l'heap è molto più complesso, perché finiscono per esserci regioni di memoria che sono inutilizzate intervallate con blocchi che lo sono: la memoria viene frammentata. Trovare memoria libera della dimensione che ti serve è un problema difficile. Questo è il motivo per cui l'heap dovrebbe essere evitato (sebbene sia ancora spesso utilizzato).
Implementazione
L'implementazione sia dello stack che dell'heap dipende solitamente dal runtime / sistema operativo. Spesso i giochi e altre applicazioni critiche per le prestazioni creano le proprie soluzioni di memoria che raccolgono una grande porzione di memoria dall'heap e quindi la distribuiscono internamente per evitare di fare affidamento sul sistema operativo per la memoria.
Questo è pratico solo se l'utilizzo della memoria è abbastanza diverso dalla norma, ovvero per i giochi in cui carichi un livello in un'unica operazione enorme e puoi buttare via l'intero lotto in un'altra operazione enorme.
Posizione fisica in memoria
Questo è meno rilevante di quanto pensi a causa di una tecnologia chiamata Memoria virtuale che fa pensare al tuo programma che tu abbia accesso a un certo indirizzo dove i dati fisici sono da qualche altra parte (anche sul disco rigido!). Gli indirizzi che ottieni per lo stack sono in ordine crescente man mano che il tuo albero delle chiamate diventa più profondo. Gli indirizzi per l'heap sono imprevedibili (cioè specifici per l'implementazione) e francamente non importanti.
|
Per chiarire, questa risposta ha informazioni errate (Thomas ha corretto la sua risposta dopo i commenti, fantastico :)). Altre risposte evitano semplicemente di spiegare cosa significa allocazione statica. Quindi spiegherò le tre forme principali di allocazione e come si relazionano solitamente all'heap, allo stack e al segmento di dati di seguito. Mostrerò anche alcuni esempi sia in C / C ++ che in Python per aiutare le persone a capire.
Le variabili "statiche" (AKA allocate staticamente) non vengono allocate nello stack. Non dare per scontato: molte persone lo fanno solo perché "statico" suona molto come "stack". In realtà non esistono né nello stack né nell'heap. Fanno parte di quello che viene chiamato il segmento di dati.
Tuttavia, è generalmente meglio considerare "ambito" e "durata" piuttosto che "stack" e "heap".
L'ambito si riferisce a quali parti del codice possono accedere a una variabile. Generalmente si pensa allo scope locale (accessibile solo dalla funzione corrente) rispetto allo scope globale (accessibile ovunque) sebbene lo scope possa diventare molto più complesso.
La durata si riferisce a quando una variabile viene allocata e deallocata durante l'esecuzione del programma. Di solito si pensa all'allocazione statica (variabilepersisterà per l'intera durata del programma, rendendola utile per memorizzare le stesse informazioni su più chiamate di funzione) rispetto all'allocazione automatica (la variabile persiste solo durante una singola chiamata a una funzione, rendendola utile per memorizzare informazioni che vengono utilizzate solo durante il tuo e può essere scartato una volta che hai finito) rispetto all'allocazione dinamica (variabili la cui durata è definita in fase di esecuzione, invece del tempo di compilazione come statica o automatica).
Sebbene la maggior parte dei compilatori e degli interpreti implementino questo comportamento in modo simile in termini di utilizzo di stack, heap, ecc., Un compilatore a volte può rompere queste convenzioni se lo desidera, purché il comportamento sia corretto. Ad esempio, a causa dell'ottimizzazione, una variabile locale può esistere solo in un registro o essere rimossa completamente, anche se la maggior parte delle variabili locali esiste nello stack. Come è stato sottolineato in alcuni commenti, sei libero di implementare un compilatore che non usa nemmeno uno stack o un heap, ma invece alcuni altri meccanismi di archiviazione (fatto raramente, poiché stack e heap sono ottimi per questo).
Fornirò un semplice codice C annotato per illustrare tutto questo. Il modo migliore per imparare è eseguire un programma con un debugger e osservare il comportamento. Se preferisci leggere python, salta alla fine della risposta :)
// Allocato staticamente nel segmento di dati quando il programma / DLL viene caricato per la prima volta
// Deallocato quando il programma / DLL viene chiuso
// scope: è possibile accedervi da qualsiasi punto del codice
int someGlobalVariable;
// Allocato staticamente nel segmento di dati quando il programma viene caricato per la prima volta
// Deallocato all'uscita dal programma / DLL
// scope: è possibile accedere da qualsiasi punto di questo particolare file di codice
static int someStaticVariable;
// "someArgument" viene allocato nello stack ogni volta che viene chiamata MyFunction
// "someArgument" viene deallocato quando MyFunction ritorna
// scope - accessibile solo da MyFunction ()
void MyFunction (int someArgument) {
// Allocato staticamente nel segmento di dati quando il programma viene caricato per la prima volta
// Deallocato all'uscita dal programma / DLL
// scope - accessibile solo da MyFunction ()
static int someLocalStaticVariable;
// Allocato nello stack ogni volta che viene chiamata MyFunction
// Deallocato quando MyFunction ritorna
// scope - accessibile solo da MyFunction ()
int someLocalVariable;
// Un * puntatore * viene allocato nello stack ogni volta che viene chiamata MyFunction
// Questo puntatore viene deallocato quando MyFunction ritorna
// scope - è possibile accedere al puntatore solo all'interno di MyFunction ()
int * someDynamicVariable;
// Questa riga fa sì che lo spazio per un numero intero venga allocato nell'heap
// quando questa riga viene eseguita. Nota che questo non è all'inizio di
// la chiamata a MyFunction (), come le variabili automatiche
// scope - solo il codice all'interno di MyFunction () può accedere a questo spazio
// * attraverso questa particolare variabile *.
// Tuttavia, se passi l'indirizzo da qualche altra parte, quel codice
// posso accedervi anche io
someDynamicVariable = new int;
// Questa riga rilascia lo spazio per il numero intero nell'heap.
// Se non lo scrivessimo, la memoria sarebbe "trapelata".
// Notare una differenza fondamentale tra lo stack e l'heap
// l'heap deve essere gestito. Lo stack è gestito per noi.
eliminare someDynamicVariable;
// In altri casi, invece di deallocare questo spazio heap tu
// potrebbe memorizzare l'indirizzo da qualche parte più permanente da utilizzare in seguito.
// Alcune lingue si occupano anche della deallocazione per te ... ma
// deve sempre essere curato in fase di esecuzione da qualche meccanismo.
// Quando la funzione ritorna, someArgument, someLocalVariable
// e il puntatore someDynamicVariable vengono deallocati.
// Lo spazio puntato da someDynamicVariable era già
// deallocato prima della restituzione.
ritorno;
}
// Notare che someGlobalVariable, someStaticVariable e
// someLocalStaticVariable continuano a esistere e non lo sono
// deallocato fino alla chiusura del programma.
Un esempio particolarmente toccante del motivo per cui è importante distinguere tra durata e ambito è che una variabile può avere un ambito locale ma una durata statica, ad esempio "someLocalStaticVariable" nell'esempio di codice sopra. Tali variabili possono rendere le nostre abitudini di denominazione comuni ma informali molto confuse. Ad esempio, quando diciamo "locale" di solito intendiamo "variabile allocata automaticamente con ambito locale" e quando diciamo globale intendiamo solitamente "variabile allocata staticamente con ambito globale". Sfortunatamente quando si tratta di cose come "variabili allocate staticamente con scope file" molte persone dicono semplicemente ... "eh ???".
Alcune delle scelte di sintassi in C / C ++ esacerbano questo problema, ad esempio molte persone pensano che le variabili globali non siano "statiche" a causa della sintassi mostrata di seguito.
int var1; // Ha ambito globale e allocazione statica
static int var2; // Ha ambito file e allocazione statica
int main () {return 0;}
Si noti che l'inserimento della parola chiave "static" nella dichiarazione precedente impedisce a var2 di avere un ambito globale. Tuttavia, la var1 globale ha un'allocazione statica. Questo non èintuitivo! Per questo motivo, cerco di non usare mai la parola "statico" quando descrivo l'ambito, ma invece di dire qualcosa come "file" o "file limitato". Tuttavia, molte persone usano la frase "statico" o "ambito statico" per descrivere una variabile a cui è possibile accedere solo da un file di codice. Nel contesto della durata, "statica" significa sempre che la variabile viene allocata all'avvio del programma e deallocata quando il programma esce.
Alcune persone pensano a questi concetti come specifici di C / C ++. Non sono. Ad esempio, l'esempio di Python di seguito illustra tutti e tre i tipi di allocazione (ci sono alcune sottili differenze possibili nei linguaggi interpretati che non entrerò qui).
da datetime import datetime
classe Animale:
_FavoriteFood = 'Undefined' # _FavoriteFood viene allocato staticamente
def PetAnimal (self):
curTime = datetime.time (datetime.now ()) # curTime viene allocato automaticamente
print ("Grazie per avermi accarezzato. Ma è" + str (curTime) + ", dovresti nutrirmi. Il mio cibo preferito è" + self._FavoriteFood)
classe Gatto (Animale):
_FavoriteFood = 'tuna' # Nota poiché sovrascriviamo, la classe Cat ha la sua variabile _FavoriteFood allocata staticamente, diversa da quella di Animal
classe Cane (Animale):
_FavoriteFood = 'steak' # Allo stesso modo, la classe Dog ottiene la propria variabile statica. Importante da notare: questa variabile statica è condivisa tra tutte le istanze di Dog, quindi non è dinamica!
se __name__ == "__main__":
baffi = Cat () # Assegnato dinamicamente
fido = Dog () # Assegnato dinamicamente
rinTinTin = Dog () # Assegnato dinamicamente
baffi.PetAnimal ()
fido.PetAnimal ()
rinTinTin.PetAnimal ()
Dog._FavoriteFood = 'milkbones'
baffi.PetAnimal ()
fido.PetAnimal ()
rinTinTin.PetAnimal ()
# L'output è:
# Grazie per avermi accarezzato. Ma sono le 13: 05: 02.255000, dovresti darmi da mangiare. Il mio cibo preferito è il tonno
# Grazie per avermi accarezzato. Ma sono le 13: 05: 02.255000, dovresti darmi da mangiare. Il mio cibo preferito è la bistecca
# Grazie per avermi accarezzato. Ma sono le 13: 05: 02.255000, dovresti darmi da mangiare. Il mio cibo preferito è la bistecca
# Grazie per avermi accarezzato. Ma sono le 13: 05: 02.255000, dovresti darmi da mangiare. Il mio cibo preferito è il tonno
# Grazie per avermi accarezzato. Ma sono le 13: 05: 02.255000, dovresti darmi da mangiare. Il mio cibo preferito sono le ossa di latte
# Grazie per avermi accarezzato. Ma sono le 13: 05: 02.256000, dovresti darmi da mangiare. Il mio cibo preferito sono le ossa di latte
|
Altri hanno risposto abbastanza bene ai tratti generali, quindi introdurrò alcuni dettagli.
Stack e heap non devono essere singolari. Una situazione comune in cui hai più di uno stack è se hai più di un thread in un processo. In questo caso ogni thread ha il proprio stack. Puoi anche avere più di un heap, ad esempio alcune configurazioni DLL possono comportare l'allocazione di DLL diverse da heap diversi, motivo per cui è generalmente una cattiva idea rilasciare la memoria allocata da una libreria diversa.
In C puoi ottenere il vantaggio dell'allocazione a lunghezza variabile attraverso l'uso di alloca, che alloca sullo stack, al contrario di alloc, che alloca sull'heap. Questa memoria non sopravviverà alla dichiarazione di ritorno, ma è utile per un buffer di memoria virtuale.
Creare un enorme buffer temporaneo su Windows di cui non usi molto non è gratuito. Questo perché il compilatore genererà un loop probe dello stack che viene chiamato ogni volta che viene inserita la funzione per assicurarsi che lo stack esista (perché Windows utilizza una singola pagina di guardia alla fine dello stack per rilevare quando è necessario aumentare lo stack. Se accedi alla memoria a più di una pagina dalla fine dello stack, andrai in crash). Esempio:
void myfunction ()
{
carattere grande [10000000];
// Fare qualcosa che viene utilizzato solo per il primo 1K del 99% delle volte.
}
|
Altri hanno risposto direttamente alla tua domanda, ma quando si cerca di capire lo stack e l'heap, penso sia utile considerare il layout della memoria di un processo UNIX tradizionale (senza thread e allocatori basati su mmap ()). La pagina Web Glossario sulla gestione della memoria ha un diagramma di questo layout di memoria.
Lo stack e l'heap si trovano tradizionalmente alle estremità opposte dello spazio degli indirizzi virtuali del processo. Lo stack cresce automaticamente quando vi si accede, fino a una dimensione impostata dal kernel (che può essere regolata con setrlimit (RLIMIT_STACK, ...)). L'heap cresce quando l'allocatore di memoria richiama la chiamata di sistema brk () o sbrk (), mappando più pagine della memoria fisica nello spazio degli indirizzi virtuali del processo.
Nei sistemi senza memoria virtuale, come alcuni sistemi incorporati, spesso si applica lo stesso layout di base, tranne per il fatto che lo stack e l'heap hanno dimensioni fisse. Tuttavia, in altri sistemi embedded (come quelli basati su microcontrollori Microchip PIC), lo stack del programma è un blocco separato di memoria che non è indirizzabile dalle istruzioni di spostamento dei dati e può essere modificato o letto indirettamente solo tramite le istruzioni del flusso del programma ritorno, ecc.). Altre architetture, come i processori Intel Itanium, hanno più stack. In questo senso, lo stack è un elemento dell'architettura della CPU.
|
La pila è una porzionedi memoria che può essere manipolata tramite diverse istruzioni in linguaggio assembly chiave, come 'pop' (rimuove e restituisce un valore dallo stack) e 'push' (sposta un valore nello stack), ma anche chiama (chiama una subroutine - questo spinge l'indirizzo per tornare allo stack) e return (ritorno da una subroutine - questo estrae l'indirizzo dallo stack e vi salta). È la regione di memoria sotto il registro del puntatore dello stack, che può essere impostato secondo necessità. Lo stack viene utilizzato anche per passare argomenti alle subroutine e anche per preservare i valori nei registri prima di chiamare le subroutine.
L'heap è una porzione di memoria che viene data a un'applicazione dal sistema operativo, tipicamente tramite una syscall come malloc. Sui sistemi operativi moderni questa memoria è un insieme di pagine a cui ha accesso solo il processo chiamante.
La dimensione dello stack viene determinata in fase di esecuzione e generalmente non aumenta dopo l'avvio del programma. In un programma C, lo stack deve essere abbastanza grande da contenere ogni variabile dichiarata all'interno di ciascuna funzione. L'heap crescerà dinamicamente secondo necessità, ma il sistema operativo alla fine sta effettuando la chiamata (spesso aumenterà l'heap di più del valore richiesto da malloc, in modo che almeno alcuni futuri mallocs non avranno bisogno di tornare al kernel ottenere più memoria. Questo comportamento è spesso personalizzabile)
Poiché hai allocato lo stack prima di avviare il programma, non è mai necessario eseguire il malloc prima di poter utilizzare lo stack, quindi questo è un leggero vantaggio. In pratica, è molto difficile prevedere cosa sarà veloce e cosa sarà lento nei moderni sistemi operativi che dispongono di sottosistemi di memoria virtuale, perché il modo in cui le pagine vengono implementate e dove sono archiviate è un dettaglio di implementazione.
|
Cos'è uno stack?
Una pila è una pila di oggetti, tipicamente disposta in modo ordinato.
Gli stack nelle architetture di elaborazione sono regioni di memoria in cui i dati vengono aggiunti o rimossi in modo last-in-first-out.
In un'applicazione multi-thread, ogni thread avrà il proprio stack.
Cos'è un mucchio?
Un mucchio è una raccolta disordinata di cose ammucchiate a casaccio.
Nelle architetture di elaborazione l'heap è un'area di memoria allocata dinamicamente che viene gestita automaticamente dal sistema operativo o dalla libreria del gestore della memoria.
La memoria nell'heap viene allocata, deallocata e ridimensionata regolarmente durante l'esecuzione del programma e questo può portare a un problema chiamato frammentazione.
La frammentazione si verifica quando gli oggetti di memoria vengono allocati con piccoli spazi intermedi troppo piccoli per contenere oggetti di memoria aggiuntivi.
Il risultato netto è una percentuale dello spazio di heap che non è utilizzabile per ulteriori allocazioni di memoria.
Entrambi insieme
In un'applicazione multi-thread, ogni thread avrà il proprio stack. Ma tutti i diversi thread condivideranno l'heap.
Poiché i diversi thread condividono l'heap in un'applicazione multi-thread, ciò significa anche che deve esserci un certo coordinamento tra i thread in modo che non provino ad accedere e manipolare gli stessi pezzi di memoria nell'heap in lo stesso tempo.
Qual è il più veloce: lo stack o l'heap? E perché?
Lo stack è molto più veloce dell'heap.
Ciò è dovuto al modo in cui la memoria viene allocata nello stack.
Allocare la memoria nello stack è semplice come spostare il puntatore dello stack verso l'alto.
Per le persone che non conoscono la programmazione, è probabilmente una buona idea utilizzare lo stack poiché è più facile.
Poiché lo stack è piccolo, dovresti usarlo quando sai esattamente quanta memoria avrai bisogno per i tuoi dati, o se sai che la dimensione dei tuoi dati è molto piccola.
È meglio usare l'heap quando sai che avrai bisogno di molta memoria per i tuoi dati, o semplicemente non sei sicuro di quanta memoria avrai bisogno (come con un array dinamico).
Modello di memoria Java
Lo stack è l'area di memoria in cui sono archiviate le variabili locali (inclusi i parametri del metodo). Quando si tratta di variabili oggetto, si tratta semplicemente di riferimenti (puntatori) agli oggetti effettivi sull'heap.
Ogni volta che viene creata un'istanza di un oggetto, un pezzo di memoria heap viene messo da parte per contenere i dati (stato) di quell'oggetto. Poiché gli oggetti possono contenere altri oggetti, alcuni di questi dati possono infatti contenere riferimenti a tali oggetti annidati.
|
Penso che molte altre persone ti abbiano dato risposte per lo più corrette su questo argomento.
Un dettaglio che è stato perso, tuttavia, è che l '"heap" dovrebbe in effetti essere probabilmente chiamato "negozio gratuito". La ragione di questa distinzione è che l'archivio gratuito originale è stato implementato con una struttura di dati nota come "heap binomiale". Per questo motivo, l'allocazione dalle prime implementazioni di malloc () / free () era un'allocazione da un mucchio. Tuttavia, in questo giorno moderno, la maggior parte dei negozi gratuiti sono implementati con strutture di dati molto elaborate che non sono cumuli binomiali.
|
Puoi fare alcune cose interessanti con lo stack. Ad esempio, hai funzioni come alloca (supponendo che tu possa superare i numerosi avvertimenti riguardanti il ​​suo utilizzo), che è una forma di malloc cheutilizza specificamente lo stack, non l'heap, per la memoria.
Detto questo, gli errori di memoria basati sullo stack sono tra i peggiori che abbia mai sperimentato. Se si utilizza la memoria heap e si oltrepassano i limiti del blocco allocato, si hanno buone possibilità di attivare un errore di segmento. (Non al 100%: il tuo blocco potrebbe essere incidentalmente contiguo con un altro che hai precedentemente allocato.) Ma poiché le variabili create sullo stack sono sempre contigue tra loro, la scrittura fuori dai limiti può cambiare il valore di un'altra variabile. Ho imparato che ogni volta che sento che il mio programma ha smesso di obbedire alle leggi della logica, è probabilmente un buffer overflow.
|
Semplicemente, lo stack è dove vengono create le variabili locali. Inoltre, ogni volta che si chiama una subroutine, il contatore del programma (puntatore alla successiva istruzione della macchina) e qualsiasi registro importante, e talvolta i parametri vengono inseriti nello stack. Quindi tutte le variabili locali all'interno della subroutine vengono inserite nello stack (e utilizzate da lì). Quando la subroutine finisce, tutte quelle cose vengono rimosse dallo stack. I dati del PC e del registro vengono recuperati e rimessi al punto in cui si trovavano quando sono stati estratti, in modo che il programma possa andare avanti per il verso giusto.
L'heap è l'area di memoria da cui vengono effettuate le allocazioni dinamiche della memoria (chiamate "nuove" o "allocate" esplicite). È una struttura dati speciale che può tenere traccia di blocchi di memoria di dimensioni variabili e del loro stato di allocazione.
Nei sistemi "classici" la RAM era strutturata in modo tale che il puntatore allo stack iniziava nella parte inferiore della memoria, il puntatore all'heap iniziava nella parte superiore e crescevano l'uno verso l'altro. Se si sovrappongono, hai esaurito la RAM. Tuttavia, ciò non funziona con i moderni sistemi operativi multi-thread. Ogni thread deve avere il proprio stack e questi possono essere creati in modo dinamico.
|
Da WikiAnwser.
Pila
Quando una funzione o un metodo chiama un'altra funzione che a sua volta chiama un'altra funzione, ecc., L'esecuzione di tutte quelle funzioni rimane sospesa finché l'ultima funzione non restituisce il suo valore.
Questa catena di chiamate di funzioni sospese è lo stack, perché gli elementi nello stack (chiamate di funzione) dipendono l'uno dall'altro.
Lo stack è importante da considerare nella gestione delle eccezioni e nelle esecuzioni dei thread.
Mucchio
L'heap è semplicemente la memoria utilizzata dai programmi per memorizzare le variabili.
Gli elementi dell'heap (variabili) non hanno dipendenze tra loro e sono sempre accessibili in modo casuale in qualsiasi momento.
|
Pila
Accesso molto veloce
Non è necessario rimuovere l'allocazione esplicita delle variabili
Lo spazio è gestito in modo efficiente dalla CPU, la memoria non verrà frammentata
Solo variabili locali
Limite sulla dimensione dello stack (dipendente dal sistema operativo)
Le variabili non possono essere ridimensionate
Mucchio
È possibile accedere alle variabili globalmente
Nessun limite alla dimensione della memoria
Accesso (relativamente) più lento
Nessun uso efficiente garantito dello spazio, la memoria può frammentarsi nel tempo man mano che i blocchi di memoria vengono allocati e quindi liberati
Devi gestire la memoria (sei responsabile dell'allocazione e della liberazione delle variabili)
Le variabili possono essere ridimensionate usando realloc ()
|
In breve
Uno stack viene utilizzato per l'allocazione della memoria statica e un heap per l'allocazione dinamica della memoria, entrambi archiviati nella RAM del computer.
In dettaglio
Lo Stack
Lo stack è una struttura dati "LIFO" (last in, first out), gestita e ottimizzata dalla CPU abbastanza da vicino. Ogni volta che una funzione dichiara una nuova variabile, viene "inserita" nello stack. Quindi ogni volta che una funzione esce, tutte le variabili inserite nello stack da quella funzione vengono liberate (vale a dire, vengono eliminate). Una volta che una variabile dello stack viene liberata, quella regione di memoria diventa disponibile per altre variabili dello stack.
Il vantaggio di utilizzare lo stack per memorizzare le variabili è che la memoria viene gestita per te. Non è necessario allocare la memoria a mano o liberarla una volta che non ne hai più bisogno. Inoltre, poiché la CPU organizza la memoria dello stack in modo così efficiente, leggere e scrivere sulle variabili dello stack è molto veloce.
Altro può essere trovato qui.
The Heap
L'heap è una regione della memoria del computer che non viene gestita automaticamente e non è gestita altrettanto strettamente dalla CPU. È una regione di memoria più libera (ed è più grande). Per allocare memoria nell'heap, è necessario utilizzare malloc () o calloc (), che sono funzioni C incorporate. Dopo aver allocato la memoria sull'heap, sei responsabile dell'utilizzo di free () per deallocare quella memoria una volta che non ne hai più bisogno.
Se non lo fai, il tuo programma avrà quella che è nota come perdita di memoria. Cioè, la memoria nell'heap verrà ancora messa da parte (e non sarà disponibile per altri processi). Come vedremo nella sezione di debug, esiste uno strumento chiamato Valgrind che può aiutarti a rilevare perdite di memoria.
A differenza dello stack, l'heap non ha limitazioni di dimensione sulla dimensione variabile (a parte le ovvie limitazioni fisiche del computer). La memoria dell'heap è leggermente più lenta da leggere e scrivere, perché è necessario utilizzare i puntatori per accedere alla memoria sull'heap. Parleremo a breve dei suggerimenti.
A differenza della pila,le variabili create sull'heap sono accessibili da qualsiasi funzione, ovunque nel programma. Le variabili di heap hanno un ambito essenzialmente globale.
Altro può essere trovato qui.
Le variabili allocate nello stack vengono archiviate direttamente nella memoria e l'accesso a questa memoria è molto veloce e la sua allocazione viene gestita quando il programma viene compilato. Quando una funzione o un metodo chiama un'altra funzione che a sua volta chiama un'altra funzione, ecc., L'esecuzione di tutte quelle funzioni rimane sospesa finché l'ultima funzione non restituisce il suo valore. Lo stack è sempre riservato in un ordine LIFO, il blocco riservato più di recente è sempre il blocco successivo da liberare. Questo rende davvero semplice tenere traccia dello stack, liberare un blocco dallo stack non è altro che regolare un puntatore.
Le variabili allocate sull'heap hanno la loro memoria allocata in fase di esecuzione e l'accesso a questa memoria è un po 'più lento, ma la dimensione dell'heap è limitata solo dalla dimensione della memoria virtuale. Gli elementi dell'heap non hanno dipendenze tra loro ed è sempre possibile accedervi in ​​modo casuale in qualsiasi momento. Puoi allocare un blocco in qualsiasi momento e liberarlo in qualsiasi momento. Ciò rende molto più complesso tenere traccia di quali parti dell'heap sono allocate o libere in un dato momento.
Puoi usare lo stack se sai esattamente quanti dati devi allocare prima della compilazione e non è troppo grande. È possibile utilizzare l'heap se non si conosce esattamente la quantità di dati necessaria in fase di esecuzione o se è necessario allocare molti dati.
In una situazione multi-thread, ogni thread avrà il proprio stack completamente indipendente, ma condividerà l'heap. Lo stack è specifico del thread e l'heap è specifico dell'applicazione. Lo stack è importante da considerare nella gestione delle eccezioni e nelle esecuzioni dei thread.
Ogni thread riceve uno stack, mentre in genere esiste un solo heap per l'applicazione (sebbene non sia raro avere più heap per diversi tipi di allocazione).
In fase di esecuzione, se l'applicazione necessita di più heap, può allocare memoria dalla memoria libera e se lo stack ha bisogno di memoria, può allocare memoria dalla memoria libera allocata per l'applicazione.
Anche qui e qui vengono forniti maggiori dettagli.
Ora vieni alle risposte alla tua domanda.
In che misura sono controllati dal sistema operativo o dal language runtime?
Il sistema operativo alloca lo stack per ogni thread a livello di sistema quando il thread viene creato. In genere il sistema operativo viene chiamato dal Language Runtime per allocare l'heap per l'applicazione.
Altro può essere trovato qui.
Qual è il loro scopo?
Già dato in alto.
"Puoi usare lo stack se sai esattamente quanti dati devi allocare prima della compilazione e non è troppo grande. Puoi usare l'heap se non sai esattamente di quanti dati avrai bisogno in runtime o se è necessario allocare molti dati. "
Altro può essere trovato qui.
Cosa determina la dimensione di ciascuno di essi?
La dimensione dello stack viene impostata dal sistema operativo quando viene creato un thread. La dimensione dell'heap viene impostata all'avvio dell'applicazione, ma può aumentare in base alla necessità di spazio (l'allocatore richiede più memoria dal sistema operativo).
Cosa ti rende più veloce?
L'allocazione dello stack è molto più veloce poiché tutto ciò che fa è spostare il puntatore dello stack. Utilizzando i pool di memoria, è possibile ottenere prestazioni comparabili dall'allocazione dell'heap, ma ciò comporta una leggera complessità aggiuntiva e le proprie grattacapi.
Inoltre, stack vs. heap non è solo una considerazione sulle prestazioni; ti dice anche molto sulla durata prevista degli oggetti.
I dettagli possono essere trovati da qui.
|
OK, semplicemente e in poche parole, intendono ordinato e non ordinato ...!
Stack: negli oggetti in pila, le cose si sovrappongono, significa che sarà più veloce ed efficiente da elaborare! ...
Quindi c'è sempre un indice per indicare l'oggetto specifico, anche l'elaborazione sarà più veloce, c'è anche una relazione tra gli articoli! ...
Heap: nessun ordine, l'elaborazione sarà più lenta ei valori sono incasinati insieme senza un ordine o indice specifico ... sono casuali e non c'è relazione tra loro ... quindi l'esecuzione e il tempo di utilizzo potrebbero variare ...
Creo anche l'immagine qui sotto per mostrare come possono apparire:
|
stack, heap e dati di ogni processo nella memoria virtuale:
|
Negli anni '80, UNIX si propagò come coniglietti con le grandi aziende che lanciavano il proprio.
Exxon ne aveva uno così come dozzine di marchi persi nella storia.
Il modo in cui la memoria è stata strutturata era a discrezione dei molti implementatori.
Un tipico programma in C è stato presentato in memoria con
un'opportunità per aumentare cambiando il valore brk ().
In genere, l'HEAP era appena al di sotto di questo valore brk
e l'aumento di brk ha aumentato la quantità di heap disponibile.
Il singolo STACK era tipicamente un'area sotto HEAP che era un tratto di memoria
non contiene nulla di valore fino all'inizio del successivo blocco di memoria fisso.
Il blocco successivo era spesso CODICE che poteva essere sovrascritto dai dati dello stack
in uno dei famosi hack della sua epoca.
Un tipico blocco di memoria era BSS (un blocco di zerovalori)
che non è stato accidentalmente azzerato nell'offerta di un produttore.
Un altro era DATA contenente valori inizializzati, comprese stringhe e numeri.
Un terzo era CODE contenente CRT (runtime C), main, funzioni e librerie.
L'avvento della memoria virtuale in UNIX cambia molti dei vincoli.
Non esiste una ragione oggettiva per cui questi blocchi devono essere contigui,
o fissato in dimensioni, o ordinato in un modo particolare ora.
Naturalmente, prima che UNIX fosse Multics che non soffriva di questi vincoli.
Ecco uno schema che mostra uno dei layout di memoria di quell'epoca.
|
Un paio di centesimi: penso, sarà bene disegnare la memoria grafica e più semplice:
Frecce: mostrano dove crescono stack e heap, la dimensione dello stack del processo ha un limite, definito nel sistema operativo, i limiti della dimensione dello stack del thread dai parametri nel thread creano l'API di solito. L'heap di solito limita la dimensione massima della memoria virtuale del processo, ad esempio per 32 bit 2-4 GB.
Modo così semplice: l'heap del processo è generale per il processo e tutti i thread all'interno, utilizzando per l'allocazione della memoria in caso comune con qualcosa come malloc ().
Stack è una memoria rapida per memorizzare in casi comuni puntatori e variabili di ritorno a funzioni, elaborati come parametri nella chiamata di funzione, variabili di funzione locali.
|
Dal momento che alcune risposte sono andate male, contribuirò con il mio acaro.
Sorprendentemente, nessuno ha menzionato che più stack di chiamate (cioè non correlati al numero di thread in esecuzione a livello di sistema operativo) si trovano non solo in linguaggi esotici (PostScript) o piattaforme (Intel Itanium), ma anche in fibre, fili verdi e alcune implementazioni di coroutines.
Fibre, fili verdi e coroutine sono per molti versi simili, il che porta a molta confusione. La differenza tra fibre e fili verdi è che i primi utilizzano il multitasking cooperativo, mentre il secondo può presentare uno cooperativo o preventivo (o anche entrambi). Per la distinzione tra fibre e coroutine, vedere qui.
In ogni caso, lo scopo di entrambe le fibre, fili verdi e coroutine è di avere più funzioni eseguite contemporaneamente, ma non in parallelo (vedere questa domanda SO per la distinzione) all'interno di un singolo thread a livello di sistema operativo, trasferendo il controllo avanti e indietro l'uno dall'altro in modo organizzato.
Quando si utilizzano fibre, fili verdi o coroutine, di solito si dispone di uno stack separato per funzione. (Tecnicamente, non solo uno stack ma un intero contesto di esecuzione è per funzione. Soprattutto, i registri della CPU.) Per ogni thread ci sono tanti stack quante sono le funzioni in esecuzione contemporaneamente e il thread passa dall'esecuzione di ciascuna funzione secondo la logica del tuo programma. Quando una funzione viene eseguita fino alla fine, il suo stack viene distrutto. Quindi, il numero e la durata degli stack sono dinamici e non sono determinati dal numero di thread a livello di sistema operativo!
Nota che ho detto "di solito hanno uno stack separato per funzione". Esistono implementazioni sia stackful che stackless di couroutines. Le implementazioni C ++ impilate più importanti sono Boost.Coroutine e async / await di Microsoft PPL. (Tuttavia, è probabile che le funzioni ripristinabili di C ++ (alias "async and await"), che sono state proposte a C ++ 17, utilizzino coroutine stackless.)
La proposta di Fibers alla libreria standard C ++ è imminente. Inoltre, ci sono alcune librerie di terze parti. I thread verdi sono estremamente popolari in linguaggi come Python e Ruby.
|
Ho qualcosa da condividere, anche se i punti principali sono già stati trattati.
Pila
Accesso molto veloce.
Memorizzato nella RAM.
Le chiamate di funzione vengono caricate qui insieme alle variabili locali e ai parametri di funzione passati.
Lo spazio viene liberato automaticamente quando il programma esce dall'ambito.
Archiviato nella memoria sequenziale.
Mucchio
Accesso lento rispetto a Stack.
Memorizzato nella RAM.
Le variabili create dinamicamente vengono memorizzate qui, il che in seguito richiede di liberare la memoria allocata dopo l'uso.
Memorizzato ovunque venga eseguita l'allocazione della memoria, accessibile sempre tramite puntatore.
Nota interessante:
Se le chiamate di funzione fossero state memorizzate nell'heap, si sarebbero ottenuti 2 punti disordinati:
A causa dell'archiviazione sequenziale nello stack, l'esecuzione è più veloce. L'archiviazione nell'heap avrebbe comportato un enorme consumo di tempo, rendendo così l'esecuzione dell'intero programma più lenta.
Se le funzioni fossero state memorizzate nell'heap (archiviazione disordinata puntata dal puntatore), non ci sarebbe stato alcun modo per tornare all'indirizzo del chiamante (che lo stack fornisce a causa della memorizzazione sequenziale in memoria).
|
Wow! Così tante risposte e non credo che una di loro abbia capito bene ...
1) Dove e cosa sono (fisicamente nella memoria di un computer reale)?
Lo stack è la memoria che inizia come l'indirizzo di memoria più alto assegnato all'immagine del programma e quindi diminuisce di valore da lì. È riservato per i parametri delle funzioni chiamate e per tutte le variabili temporanee utilizzate nelle funzioni.
Ci sono due cumuli: pubblico e privato.
L'heap privato inizia su un limite di 16 byte (per programmi a 64 bit) o ​​un limite di 8 byte (per programmi a 32 bit) dopo l'ultimo byte di codice nel programma e quindi aumentavalore da lì. Viene anche chiamato heap predefinito.
Se l'heap privato diventa troppo grande, si sovrapporrà all'area dello stack, così come lo stack si sovrapporrà all'heap se diventa troppo grande. Poiché lo stack inizia da un indirizzo più alto e scende fino a un indirizzo più basso, con un corretto hacking è possibile ottenere lo stack così grande da superare l'area dell'heap privato e sovrapporsi all'area del codice. Il trucco quindi è quello di sovrapporre una quantità sufficiente dell'area del codice da poter essere agganciata al codice. È un po 'complicato da fare e rischi un crash del programma, ma è facile e molto efficace.
L'heap pubblico risiede nel proprio spazio di memoria al di fuori dello spazio dell'immagine del programma. È questa memoria che verrà trasferita sul disco rigido se le risorse di memoria scarseggiano.
2) In che misura sono controllati dal sistema operativo o dal language runtime?
Lo stack è controllato dal programmatore, l'heap privato è gestito dal sistema operativo e l'heap pubblico non è controllato da nessuno perché è un servizio del sistema operativo: si effettuano richieste e vengono concesse o negate.
2b) Qual è il loro scopo?
Sono tutti globali per il programma, ma i loro contenuti possono essere privati, pubblici o globali.
2c) Cosa determina la dimensione di ciascuno di essi?
La dimensione dello stack e dell'heap privato sono determinate dalle opzioni di runtime del compilatore. L'heap pubblico viene inizializzato in fase di esecuzione utilizzando un parametro size.
2d) Cosa rende uno più veloce?
Non sono progettati per essere veloci, sono progettati per essere utili. Il modo in cui il programmatore li utilizza determina se sono "veloci" o "lenti"
RIF:
https://norasandler.com/2019/02/18/Write-a-Compiler-10.html
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate
|
Molte risposte sono corrette come concetti, ma dobbiamo notare che uno stack è necessario all'hardware (cioè microprocessore) per consentire la chiamata di subroutine (CALL in linguaggio assembly ..). (I ragazzi di OOP lo chiameranno metodi)
Nello stack si salvano gli indirizzi di ritorno e la chiamata → push / ret → pop è gestita direttamente nell'hardware.
Puoi usare lo stack per passare i parametri .. anche se è più lento dell'uso dei registri (direbbe un guru del microprocessore o un buon libro del BIOS degli anni '80 ...)
Senza stack nessun microprocessore può funzionare. (non possiamo immaginare un programma, anche in linguaggio assembly, senza subroutine / funzioni)
Senza il mucchio può. (Un programma in linguaggio assembly può funzionare senza, poiché l'heap è un concetto di sistema operativo, come malloc, ovvero una chiamata OS / Lib.
L'utilizzo dello stack è più veloce in quanto:
È hardware, e anche push / pop sono molto efficienti.
malloc richiede di entrare in modalità kernel, usare lock / semaphore (o altre primitive di sincronizzazione) eseguendo del codice e gestendo alcune strutture necessarie per tenere traccia dell'allocazione.
|
L'heap è un'area di memoria allocata dinamicamente che viene gestita automaticamente dal sistema operativo o dalla libreria del gestore della memoria. Puoi allocare un blocco in qualsiasi momento e liberarlo in qualsiasi momento. L'allocazione dell'heap richiede il mantenimento di una registrazione completa di quale memoria è allocata e cosa non lo è, oltre a una manutenzione generale per ridurre la frammentazione, trovare segmenti di memoria contigui abbastanza grandi da adattarsi alla dimensione richiesta e così via. La memoria può essere rilasciata in qualsiasi momento lasciando spazio libero. Man mano che l'heap cresce, i nuovi blocchi vengono spesso allocati da indirizzi inferiori a indirizzi superiori. Quindi puoi pensare all'heap come a un mucchio di blocchi di memoria che cresce di dimensioni man mano che la memoria viene allocata. Se l'heap è troppo piccolo per un'allocazione, la dimensione può spesso essere aumentata acquisendo più memoria dal sistema operativo sottostante. La memoria allocata dall'heap rimarrà allocata fino a quando non si verifica una delle seguenti condizioni:
La memoria è liberata
Il programma termina
Pila:
Memorizzato nella RAM del computer proprio come l'heap.
Le variabili create nello stack usciranno dall'ambito e verranno deallocate automaticamente.
Molto più veloce da allocare rispetto alle variabili nell'heap.
Memorizza i dati locali, gli indirizzi di ritorno, utilizzati per il passaggio dei parametri.
Può avere un overflow dello stack quando viene utilizzata una quantità eccessiva di stack (principalmente
da ricorsione infinita o troppo profonda, allocazioni molto grandi).
Utilizzeresti lo stack se sapessi esattamente quanti dati hai bisogno
allocare prima del tempo di compilazione e non è troppo grande.
Di solito ha una dimensione massima già determinata quando il tuo programma
inizia.
Mucchio:
Memorizzato nella RAM del computer proprio come lo stack.
In C ++, le variabili sull'heap devono essere eliminate manualmente e mai
cadono fuori campo.
I dati vengono liberati con delete, delete [] o free.
Più lento da allocare rispetto alle variabili nello stack.
Utilizzato su richiesta per allocare un blocco di dati per l'utilizzo da parte del programma.
Può avere frammentazione quando sono presenti molte allocazioni e
deallocazioni.
In C ++ o C, i dati creati sull'heap saranno puntati da puntatori
e allocati rispettivamente con new o malloc.
Può avere errori di allocazione se viene richiesto un buffer troppo grande
essere assegnato.
tuuserebbe l'heap se non sai esattamente quanti dati hai
sarà necessario in fase di esecuzione o se è necessario allocare molti dati.
Responsabile per perdite di memoria.
|
Lo stack è essenzialmente una memoria di facile accesso che gestisce semplicemente i suoi elementi
come - beh - stack. Solo gli oggetti di cui si conosce la dimensione in anticipo possono essere messi in pila. Questo è il caso di numeri, stringhe, booleani.
L'heap è una memoria per elementi di cui non è possibile predeterminare
dimensione e struttura esatte. Poiché oggetti e array possono essere modificati e
cambiare in fase di esecuzione, devono andare nell'heap.
Fonte: Academind
|
Lo stack e l'heap della CPU sono fisicamente correlati a come la CPU ei registri funzionano con la memoria, come funziona il linguaggio di assemblaggio della macchina, non i linguaggi di alto livello stessi, anche se questi linguaggi possono decidere piccole cose.
Tutte le CPU moderne funzionano con la "stessa" teoria dei microprocessori: sono tutte basate su quelli che vengono chiamati "registri" e alcune sono per lo "stack" per ottenere prestazioni. Tutte le CPU hanno registri di stack dall'inizio ed erano sempre state qui, per modo di parlare, come so. I linguaggi Assembly sono gli stessi dall'inizio, nonostante le variazioni ... fino a Microsoft e il suo Intermediate Language (IL) che ha cambiato il paradigma per avere un linguaggio assembly per macchina virtuale OO. Quindi saremo in grado di avere alcune CPU CLI / CIL in futuro (un progetto di MS).
Le CPU hanno registri di stack per velocizzare l'accesso alle memorie, ma sono limitati rispetto all'uso di altri registri per ottenere pieno accesso a tutta la memoria disponibile per il processus. È per questo che abbiamo parlato di allocazioni di stack e heap.
In sintesi, e in generale, l'heap è lento e lento ed è per istanze "globali" e contenuto di oggetti, poiché lo stack è piccolo e veloce e per variabili e riferimenti "locali" (puntatori nascosti per dimenticarsi di gestirli).
Quindi, quando usiamo la nuova parola chiave in un metodo, il riferimento (un int) viene creato nello stack, ma l'oggetto e tutto il suo contenuto (tipi di valore e oggetti) vengono creati nell'heap, se ricordo. Ma i tipi di valore elementari locali e gli array vengono creati nello stack.
La differenza nell'accesso alla memoria è a livello di riferimento delle celle: indirizzare l'heap, la memoria complessiva del processo, richiede più complessità in termini di gestione dei registri della CPU, rispetto allo stack che è "più" localmente in termini di indirizzamento perché lo stack della CPU register è usato come indirizzo di base, se ricordo.
Questo è il motivo per cui quando abbiamo chiamate o cicli di ricorsione molto lunghi o infiniti, abbiamo un overflow dello stack rapidamente, senza congelare il sistema sui computer moderni ...
C # Heap (ing) Vs Stack (ing) in .NET
Stack vs Heap: conosci la differenza
Allocazione di memoria della classe statica in cui è archiviata C #
Cosa e dove sono lo stack e l'heap?
https://en.wikipedia.org/wiki/Memory_management
https://en.wikipedia.org/wiki/Stack_register
Risorse in linguaggio Assembly:
Tutorial sulla programmazione di assembly
Manuali per sviluppatori software per architetture Intel® 64 e IA-32
|
Grazie per l'ottima discussione, ma da vero noob mi chiedo dove siano conservate le istruzioni? All'inizio gli scienziati stavano decidendo tra due architetture (von NEUMANN dove tutto è considerato DATA e HARVARD dove un'area di memoria era riservata alle istruzioni e un'altra ai dati). Alla fine, siamo andati con il design di von Neumann e ora tutto è considerato "lo stesso". Questo ha reso difficile per me quando stavo imparando l'assemblea
https://www.cs.virginia.edu/~evans/cs216/guides/x86.html
perché parlano di registri e puntatori allo stack.
Tutto sopra parla di DATI. La mia ipotesi è che poiché un'istruzione è una cosa definita con uno specifico footprint di memoria, andrebbe nello stack e quindi tutti i registri "quei" discussi in assembly sono sullo stack. Naturalmente poi è arrivata la programmazione orientata agli oggetti con istruzioni e dati inseriti in una struttura dinamica, quindi ora anche le istruzioni sarebbero state mantenute nell'heap?
|
Domanda molto attiva. Guadagna 10 punti reputazione per rispondere a questa domanda. Il requisito di reputazione aiuta a proteggere questa domanda dallo spam e dalle attività di mancata risposta.
Non è la risposta che stai cercando? Sfoglia altre domande contrassegnate come stack di gestione della memoria heap indipendente dalla lingua per allocazione dinamica della memoria o poni la tua domanda.